#include <Core/Commands.h>
#include <Core/BoxApi/ApiManager.h>

#include <iostream>
#include <boost/thread.hpp>
#include <boost/function.hpp>
#include <boost/program_options.hpp>

/**
 * Helpers
 */
typedef std::list
		<
			std::pair
			<
				Core::ICommandPtr, 
				Core::BoxApi::IBoxConnectionPtr
			> 
		> MessagesType;

Core::BoxApi::ApiManagerParams const amParams = { 1, true };	
Core::BoxApi::ApiManager apiManager(amParams);
boost::mutex outLock;

void die(std::string const & message, int code) 
{
	std::cout << "Error : " << message << ", code " << code << std::endl;
	std::exit(code);
}

Core::BoxApi::IBoxConnectionPtr 
	sendCommandTo(std::string const & host, std::string const & port, Core::ICommandPtr command) 
{
	Core::BoxApi::BoxConnectionParams connArgs = { host, port, "conn", 0 };
	Core::BoxApi::IBoxConnectionPtr currConn = apiManager.connect(connArgs);
	apiManager.postMessage(currConn, command);
	boost::mutex::scoped_lock lock(outLock);
	std::cout << "Message send to { " + host + ", " + port + " }" << std::endl;
	return currConn;
}

void handleServerReply(
	Core::BoxApi::IBoxConnectionPtr currConn, boost::function<void (Core::ICommandPtr)> commandHandling) 
{
	if (!currConn)
		return;
	sleep(1);	
	MessagesType messages = apiManager.getNextMessages(0 /* means all pending messages */ );
	for (MessagesType::iterator first = messages.begin(), last = messages.end(); 
		first != last;
		++first) 
	{
		Core::ICommandPtr command = first->first;
		Core::BoxApi::IBoxConnectionPtr currConn = first->second;
		if (command->getTag() == Core::ErrorCommand::tag) {
			Core::ErrorCommandPtr err = Core::commandCast<Core::ErrorCommand>(command);
			boost::mutex::scoped_lock lock(outLock);
			std::cerr << "Got error : " << err->getMessage() << ", message count : " << apiManager.getMessageCount() << std::endl;
			return;
		}
		std::cout << "Got message : TAG " << command->getTag() << ", message count : " << apiManager.getMessageCount() << std::endl;
		commandHandling(command);
	}
}

Core::BoxApi::IBoxConnectionPtr handleCommandLineArgs(int argc, char ** argv) 
{
	namespace po = boost::program_options;

	std::string host, port, file, array;

	po::variables_map vm;
	po::options_description desc("Allowed options");
	desc.add_options()
	    ("help", "produce help message") 
		("host", po::value<std::string>(&host)->default_value("127.0.0.1"), "host")	
		("port", po::value<std::string>(&port)->default_value("5999"), "port")
		("scan_file", po::value<std::string>(&file), "path to file")
		("scan_array", po::value<std::string>(&array)->default_value(""), "scan data from stdin [NOT IMPLEMENTED]");
	
	po::store(po::parse_command_line(argc, argv, desc), vm);
	po::notify(vm);

	if (vm.count("help")) {
		std::cout << desc << std::endl;
		std::exit(0);
	}

	if (vm.count("scan_file"))
		return sendCommandTo(host, port, 
			Core::FileScanCommandPtr(new Core::FileScanCommand(file)));
	
	if (vm.count("scan_array"))
		die("scan_array [NOT IMPLEMENTED]", EXIT_FAILURE);

	return Core::BoxApi::IBoxConnectionPtr(); 
}

void commandToStdOut(Core::ICommandPtr command) 
{
	if (Core::GuidReplyCommandPtr guid = Core::commandCast<Core::GuidReplyCommand>(command)) {
		if (!guid->getGuid().empty()) 
			std::cout << "Found infection : " << guid->getGuid() << std::endl;
		else
			std::cout << "File not infected : " << std::endl;
	} if (Core::OkCommandPtr ok = Core::commandCast<Core::OkCommand>(command)) {
		std::cout << "Ok reply recv. from the server" << std::endl;
	}
}

/**
 * main
 */
int main(int argc, char ** argv) 
try 
{
	Core::BoxApi::IBoxConnectionPtr conn = handleCommandLineArgs(argc, argv);
	handleServerReply(conn, &commandToStdOut);	
	
	std::cout << "Press 'Enter' to exit..." << std::endl;
	std::cin.get();
	return 0;
} 
catch (std::exception const & expt) 
{
	die(expt.what(), EXIT_FAILURE);
}

